package cn.easyutil.veteran.core.condition;

import cn.easyutil.veteran.core.error.CommonException;
import cn.easyutil.veteran.core.utils.AssertUtil;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;

import javax.script.Bindings;
import javax.script.CompiledScript;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;

public class JSExpressionParser {

    private static final NashornSandbox sandbox = NashornSandboxes.create();
    {
        sandbox.setMaxCPUTime(500);// 设置脚本执行允许的最大CPU时间（以毫秒为单位），超过则会报异常,防止死循环脚本
        sandbox.setMaxMemory(1024 * 1024); //设置JS执行程序线程可以分配的最大内存（以字节为单位），超过会报ScriptMemoryAbuseException错误
        sandbox.allowNoBraces(true); // 是否允许使用大括号
        sandbox.allowLoadFunctions(true); // 是否允许nashorn加载全局函数
        sandbox.setMaxPreparedStatements(30); // because preparing scripts for execution is expensive // LRU初缓存的初始化大小，默认为0
        sandbox.setExecutor(Executors.newSingleThreadExecutor());// 指定执行程序服务，该服务用于在CPU时间运行脚本
    }

    //进行预编译过的方法
    private static final Map<String, CompiledScript> initFunctions = new ConcurrentHashMap<>();


    /**
     * 执行脚本
     * @param funCanonicalName  脚本方法全名 示例：test1(a,b,c)
     * @param funBody   脚本执行体 示例：return a+b+c;
     * @param params    脚本执行需要的参数，非必须
     * @return  脚本执行结果
     */
    public static Object doScript(String funCanonicalName,String funBody, Map<String,Object> params){
        AssertUtil.isEmpty(funCanonicalName,"脚本方法名称不可为空");
        AssertUtil.isEmpty(funBody,"脚本内容不可为空");
        StringBuilder sb = new StringBuilder();
        if(!funCanonicalName.trim().startsWith("function")){
            sb.append("function ");
        }
        sb.append(funCanonicalName);
        if(!funBody.trim().startsWith("{")){
            sb.append("{");
        }
        sb.append(funBody);
        if(!funBody.trim().endsWith("}")){
            sb.append("}");
        }
        sb.append(funCanonicalName.replace("function ",""));
        String function = sb.toString();
        try {
            Bindings bindings = sandbox.createBindings();
            bindings.putAll(Optional.ofNullable(params).orElse(Collections.emptyMap()));
            if(!initFunctions.containsKey(function)){
                initFunctions.put(function,sandbox.compile(function));
            }
            CompiledScript compiledScript = initFunctions.get(function);
            return compiledScript.eval(bindings);
        }catch (Exception e) {
            throw new CommonException(e);
        }
    }
}
